#include "RenderWindow.hpp"
#include <iostream>
#include "RenderingProcesser.hpp"
#include <thread>
#include <chrono>
#include "Shader.hpp"

// Change PHX_OPENGL_VERSION_MAJOR and PHX_OPENGL_VERSION_MINOR to change opengl version
#define PHX_OPENGL_VERSION_MAJOR 4
#define PHX_OPENGL_VERSION_MINOR 1
#define PHX_OPENGL_VERSION_STRING "GL_VERSION_4_1"

namespace phx
{

struct CallbackInterface
{
	inline void keyCallbackFunc(RenderWindow *window, int key, int scancode, int action, int mods)
	{
		if(window->mKeyCallbackFunc)
			window->mKeyCallbackFunc(*window, key, scancode, action, mods);
	}

	inline void mouseButtonCallbackFunc(RenderWindow *window, int button, int action, int mods)
	{
		if(window->mMouseButtonCallbackFunc)
			window->mMouseButtonCallbackFunc(*window, ygg::Mouse::getButtonFromGLFW(button), ygg::Mouse::getStateFromGLFW(action));
	}

	inline void scrollCallbackFunc(RenderWindow *window, double offsetX, double offsetY)
	{
		if(window->mMouseButtonCallbackFunc)
		{
			ygg::Mouse::Button button = ygg::Mouse::SCROLL_UP;
			if(offsetY < 0.0) // offsetY will be -1 when scrolling down
				button = ygg::Mouse::SCROLL_DOWN;
			window->mMouseButtonCallbackFunc(*window, button, ygg::Mouse::PRESS);
		}
	}
};

static CallbackInterface callbackInterface;

static bool glewInitialized = false;

static void keyCallbackFunc(GLFWwindow *window, int key, int scancode, int action, int mods)
{
	void *userPointer = glfwGetWindowUserPointer(window);
	if(userPointer)
		callbackInterface.keyCallbackFunc((RenderWindow*)userPointer, key, scancode, action, mods);
}

static void mouseButtonCallbackFunc(GLFWwindow *window, int button, int action, int mods)
{
	void *userPointer = glfwGetWindowUserPointer(window);
	if(userPointer)
		callbackInterface.mouseButtonCallbackFunc((RenderWindow*)userPointer, button, action, mods);
}

static void scrollCallbackFunc(GLFWwindow *window, double offsetX, double offsetY)
{
	void *userPointer = glfwGetWindowUserPointer(window);
	if(userPointer)
		callbackInterface.scrollCallbackFunc((RenderWindow*)userPointer, offsetX, offsetY);
}

static void closeCallbackFunc(GLFWwindow *window)
{
	
}

static void errorCallback(int error, const char *description)
{
	std::cerr<<"GLFW ERROR CODE["<<error<<"]: "<<description<<std::endl;
}

RenderWindow::RenderWindow(GLint width, GLint height, char *title) : 
	mWidth(width),
	mHeight(height),
	mTitle(title),
	mKeyCallbackFunc(),
	mFramerateLimit(0),
	mCamera(nullptr),
	mRenderCallbackFunc(nullptr)
{
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, PHX_OPENGL_VERSION_MAJOR);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, PHX_OPENGL_VERSION_MINOR);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	#ifdef PHX_DEBUG
		glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
	#endif

	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
	glfwWindowHint(GLFW_SAMPLES, 8);
	//glEnable(GL_MULTISAMPLE_ARB);

	mGLFWwindow = glfwCreateWindow(mWidth, mHeight, title, nullptr, nullptr);
	glfwMakeContextCurrent(mGLFWwindow);

	if(!glewInitialized)
	{
		glewExperimental = GL_TRUE;
		if(glewInit() != GLEW_OK)
		{
			printf("Failed to initialize GLEW\n");
			std::this_thread::sleep_for(std::chrono::milliseconds(3000));
			exit(EXIT_FAILURE);
		}
		glewInitialized = true;
		if(!glewIsSupported(PHX_OPENGL_VERSION_STRING))
		{
			printf("%s is not supported\n", PHX_OPENGL_VERSION_STRING);
			std::this_thread::sleep_for(std::chrono::milliseconds(3000));
			exit(EXIT_FAILURE);
		}
		DefaultShader defaultShaderInitializer;

		// Remove all errors generated by glew from the error stack
		while(glGetError() != GL_NO_ERROR){}
	}

	glfwSetWindowUserPointer(mGLFWwindow, (void*)this);

	#ifdef PHX_DEBUG
		const GLubyte *renderer = glGetString(GL_RENDERER);
		std::cout<<"Renderer: "<<renderer<<std::endl;
	#endif

	glEnable(GL_DEPTH_TEST);
	glFrontFace(GL_CCW);
	//glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glEnable(GL_DITHER);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	//glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

	glfwSetKeyCallback(mGLFWwindow, keyCallbackFunc);
	glfwSetMouseButtonCallback(mGLFWwindow, mouseButtonCallbackFunc);
	glfwSetWindowCloseCallback(mGLFWwindow, closeCallbackFunc);
	glfwSetScrollCallback(mGLFWwindow, scrollCallbackFunc);
	glfwSetErrorCallback(errorCallback);

	glfwSwapInterval(0);

	RenderingProcesser::getInstance().addWindow(this);

	//glViewport(0, 0, mWidth, mHeight);
}

bool RenderWindow::isOpen()
{
	return !glfwWindowShouldClose(mGLFWwindow);
}

void RenderWindow::display()
{
	//glFlush();
	SwapBuffers(GetDC(glfwGetWin32Window(mGLFWwindow)));
	//glfwSwapBuffers(mGLFWwindow);
}

void RenderWindow::clear(const phx::Colorf &color)
{
	glClearStencil(0);
	glClearColor(color.r, color.g, color.b, color.a);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void RenderWindow::close()
{
	glfwSetWindowShouldClose(mGLFWwindow, GL_TRUE);
}

void RenderWindow::pollEvent()
{
	glfwPollEvents();
}

vec2f RenderWindow::getSize()
{
	vec2i size;
	glfwGetWindowSize(mGLFWwindow, &size.width, &size.height);
	return vec2f(size.width, size.height);
}

vec2f RenderWindow::getPosition()
{
	vec2i pos;
	glfwGetWindowPos(mGLFWwindow, &pos.x, &pos.y);
	return vec2f(pos.x, pos.y);
}

void RenderWindow::setKeyCallbackFunc(std::function<void(RenderWindow&, int, int, int, int)> func)
{
	mKeyCallbackFunc = func;
}

void RenderWindow::setMouseButtonCallbackFunc(std::function<void(RenderWindow&, ygg::Mouse::Button, ygg::Mouse::ButtonState)> func)
{
	mMouseButtonCallbackFunc = func;
}

void RenderWindow::setFramerateLimit(GLint limit)
{
	mFramerateLimit = limit;
}

void RenderWindow::setRenderCallbackFunc(std::function<void(RenderWindow&, unsigned int)> func)
{
	mRenderCallbackFunc = func;
}

void RenderWindow::useCamera(Camera &camera)
{
	mCamera = &camera;
}

bool RenderWindow::isCursorVisible()
{
	int cursorMode = glfwGetInputMode(mGLFWwindow, GLFW_CURSOR);
	return cursorMode == GLFW_CURSOR_NORMAL ? true : false;
}

void RenderWindow::setCursorVisible(bool visible)
{
	glfwSetInputMode(mGLFWwindow, GLFW_CURSOR, visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_HIDDEN);
}

RenderWindow::~RenderWindow()
{
	// Call rendering processer to check if this window is in its list, if it is; then remove it first from there and possibly end main loop if all windows are destroyed.
	glfwDestroyWindow(mGLFWwindow);
}

}